import 'package:dio/dio.dart';
import 'package:dio/src/interceptors/imply_content_type.dart';

import '../DioExtens.dart';
import '../common/test_page.dart';
import '../mock/adapters.dart';

class MyInterceptor extends Interceptor {
  int requestCount = 0;

  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    requestCount++;
    return super.onRequest(options, handler);
  }
}

class InterceptorTestPage extends TestPage {
  InterceptorTestPage(super.title){
    group('Interceptor test', () {
      test('Dio().interceptors..add(InterceptorsWrapper())', () async {
        final dio = getDio();
        dio.options.baseUrl = EchoAdapter.mockBase;
        dio.httpClientAdapter = EchoAdapter();
        dio.interceptors
          ..add(
            InterceptorsWrapper(
              onRequest: (reqOpt, handler) {
                switch (reqOpt.path) {
                  case '/resolve':
                    handler.resolve(Response(requestOptions: reqOpt, data: 1));
                    break;
                  case '/resolve-next':
                    handler.resolve(
                      Response(requestOptions: reqOpt, data: 2),
                      true,
                    );
                    break;
                  case '/resolve-next/always':
                    handler.resolve(
                      Response(requestOptions: reqOpt, data: 2),
                      true,
                    );
                    break;
                  case '/resolve-next/reject':
                    handler.resolve(
                      Response(requestOptions: reqOpt, data: 2),
                      true,
                    );
                    break;
                  case '/resolve-next/reject-next':
                    handler.resolve(
                      Response(requestOptions: reqOpt, data: 2),
                      true,
                    );
                    break;
                  case '/reject':
                    handler
                        .reject(DioException(requestOptions: reqOpt, error: 3));
                    break;
                  case '/reject-next':
                    handler.reject(
                      DioException(requestOptions: reqOpt, error: 4),
                      true,
                    );
                    break;
                  case '/reject-next/reject':
                    handler.reject(
                      DioException(requestOptions: reqOpt, error: 5),
                      true,
                    );
                    break;
                  case '/reject-next-response':
                    handler.reject(
                      DioException(requestOptions: reqOpt, error: 5),
                      true,
                    );
                    break;
                  default:
                    handler.next(reqOpt); //continue
                }
              },
              onResponse: (response, ResponseInterceptorHandler handler) {
                final options = response.requestOptions;
                switch (options.path) {
                  case '/resolve':
                    throw 'unexpected1';
                  case '/resolve-next':
                    response.data++;
                    handler.resolve(response); //3
                    break;
                  case '/resolve-next/always':
                    response.data++;
                    handler.next(response); //3
                    break;
                  case '/resolve-next/reject':
                    handler.reject(
                      DioException(
                        requestOptions: options,
                        error: '/resolve-next/reject',
                      ),
                    );
                    break;
                  case '/resolve-next/reject-next':
                    handler.reject(
                      DioException(requestOptions: options, error: ''),
                      true,
                    );
                    break;
                  default:
                    handler.next(response); //continue
                }
              },
              onError: (err, handler) {
                if (err.requestOptions.path == '/reject-next-response') {
                  handler.resolve(
                    Response(
                      requestOptions: err.requestOptions,
                      data: 100,
                    ),
                  );
                } else if (err.requestOptions.path ==
                    '/resolve-next/reject-next') {
                  handler.next(err.copyWith(error: 1));
                } else {
                  if (err.requestOptions.path == '/reject-next/reject') {
                    handler.reject(err);
                  } else {
                    int count = err.error as int;
                    count++;
                    handler.next(err.copyWith(error: count));
                  }
                }
              },
            ),
          )
          ..add(
            InterceptorsWrapper(
              onRequest: (options, handler) => handler.next(options),
              onResponse: (response, handler) {
                final options = response.requestOptions;
                switch (options.path) {
                  case '/resolve-next/always':
                    response.data++;
                    handler.next(response); //4
                    break;
                  default:
                    handler.next(response); //continue
                }
              },
              onError: (err, handler) {
                if (err.requestOptions.path == '/resolve-next/reject-next') {
                  int count = err.error as int;
                  count++;
                  handler.next(err.copyWith(error: count));
                } else {
                  int count = err.error as int;
                  count++;
                  handler.next(err.copyWith(error: count));
                }
              },
            ),
          );
        Response response = await dio.get('/resolve');
        expect(response.data, 1);
        response = await dio.get('/resolve-next');

        expect(response.data, 3);

        response = await dio.get('/resolve-next/always');
        expect(response.data, 4);

        response = await dio.post('/post', data: 'xxx');
        expect(response.data, 'xxx');

        response = await dio.get('/reject-next-response');
        expect(response.data, 100);

        expect(
          dio.get('/reject').catchError((e) => throw e.error as num),
          3,
        );

        expect(
          dio.get('/reject-next').catchError((e) => throw e.error as num),
          6,
        );

        expect(
          dio.get('/reject-next/reject').catchError((e) => throw e.error as num),
          5,
        );

        expect(
          dio
              .get('/resolve-next/reject')
              .catchError((e) => throw e.error as Object),
          '/resolve-next/reject',
        );

        expect(
          dio
              .get('/resolve-next/reject-next')
              .catchError((e) => throw e.error as num),
          2,
        );
      });

      test('Dio().interceptors..add(InterceptorsWrapper()) 设置意外错误的显示', () async {
        final dio = getDio();
        dio.options.baseUrl = EchoAdapter.mockBase;
        dio.httpClientAdapter = EchoAdapter();
        dio.interceptors.add(
          InterceptorsWrapper(
            onRequest: (reqOpt, handler) {
              if (reqOpt.path == '/error') {
                throw 'unexpected';
              }
              handler.next(reqOpt.copyWith(path: '/xxx'));
            },
            onError: (err, handler) {
              handler.next(err.copyWith(error: 'unexpected error'));
            },
          ),
        );

        expect(
          dio.get('/error').catchError((e) => throw e.error as String),
          'unexpected error',
        );

        expect(
          dio.get('/').then((e) => throw e.requestOptions.path),
          '/xxx',
        );
      });

      test('Dio().interceptors..add(InterceptorsWrapper()) 设置请求拦截器', () async {
        final dio = Dio();
        dio.options.baseUrl = MockAdapter.mockBase;
        dio.httpClientAdapter = MockAdapter();
        dio.interceptors.add(
          InterceptorsWrapper(
            onRequest: (
                RequestOptions options,
                RequestInterceptorHandler handler,
                ) {
              switch (options.path) {
                case '/fakepath1':
                  handler.resolve(
                    Response(
                      requestOptions: options,
                      data: 'fake data',
                    ),
                  );
                  break;
                case '/fakepath2':
                  dio
                      .get('/test')
                      .then(handler.resolve)
                      .catchError((e) => handler.reject(e as DioException));
                  break;
                case '/fakepath3':
                  handler.reject(
                    DioException(
                      requestOptions: options,
                      error: 'test error',
                    ),
                  );
                  break;
                case '/fakepath4':
                  handler.reject(
                    DioException(
                      requestOptions: options,
                      error: 'test error',
                    ),
                  );
                  break;
                case '/test?tag=1':
                  dio.get('/token').then((response) {
                    options.headers['token'] = response.data['data']['token'];
                    handler.next(options);
                  });
                  break;
                default:
                  handler.next(options); //continue
              }
            },
          ),
        );

        Response response = await dio.get('/fakepath1');
        expect(response.data, 'fake data');

        response = await dio.get('/fakepath2');
        expect(response.data['errCode'], 0);

        expect(
          dio.get('/fakepath3'),
          '',
        );
        expect(
          dio.get('/fakepath4'),
          '',
        );

        response = await dio.get('/test');
        expect(response.data['errCode'], 0);
        response = await dio.get('/test?tag=1');
        expect(response.data['errCode'], 0);
      });

      group('Dio().interceptors 隐含内容拦截器', () {
        Dio createDio() {
          final dio = getDio();
          dio.options.baseUrl = EchoAdapter.mockBase;
          dio.httpClientAdapter = EchoAdapter();
          return dio;
        }

        test('dio.interceptors', () async {
          final dio = createDio();
          expect(
            dio.interceptors.whereType<ImplyContentTypeInterceptor>(),
            'isNotEmpty',
          );
        });

        test('dio.interceptors.removeImplyContentTypeInterceptor()', () async {
          final dio = createDio();
          dio.interceptors.removeImplyContentTypeInterceptor();
          expect(
            dio.interceptors.whereType<ImplyContentTypeInterceptor>(),
            'isEmpty',
          );
        });

        test('忽略空数据', () async {
          final dio = createDio();
          final response = await dio.get('/echo');
          expect(response.requestOptions.contentType, 'isNull');
        });

        test('不覆盖现有内容类型', () async {
          final dio = createDio();
          final response = await dio.get(
            '/echo',
            data: 'hello',
            options: Options(headers: {'Content-Type': 'text/plain'}),
          );
          expect(response.requestOptions.contentType, 'text/plain');
        });

        test('忽略不支持的数据类型', () async {
          final dio = createDio();
          final response = await dio.get('/echo', data: 42);
          expect(response.requestOptions.contentType, 'isNull');
        });

        test('为字符串实例设置application/json', () async {
          final dio = createDio();
          final response = await dio.get('/echo', data: 'hello');
          expect(response.requestOptions.contentType, 'application/json');
        });

        test('为Map实例设置application/json', () async {
          final dio = createDio();
          final response = await dio.get('/echo', data: {'hello': 'there'});
          expect(response.requestOptions.contentType, 'application/json');
        });

        test('为List＜Map＞实例设置application/json', () async {
          final dio = createDio();
          final response = await dio.get(
            '/echo',
            data: [
              {'hello': 'here'},
              {'hello': 'there'}
            ],
          );
          expect(response.requestOptions.contentType, 'application/json');
        });

        test('为FormData实例设置多部分/表单数据: response.requestOptions.contentType?.split(";").first,', () async {
          final dio = createDio();
          final response = await dio.get(
            '/echo',
            data: FormData.fromMap({'hello': 'there'}),
          );
          expect(
            response.requestOptions.contentType?.split(';').first,
            'multipart/form-data',
          );
        });
      });
    });

    group('响应拦截器', () {
      Dio dio;
      test('响应拦截器', () async {
        const urlNotFound = '/404/';
        const urlNotFound1 = '${urlNotFound}1';
        const urlNotFound2 = '${urlNotFound}2';
        const urlNotFound3 = '${urlNotFound}3';

        dio = getDio();
        dio.httpClientAdapter = MockAdapter();
        dio.options.baseUrl = MockAdapter.mockBase;

        dio.interceptors.add(
          InterceptorsWrapper(
            onResponse: (response, handler) {
              response.data = response.data['data'];
              handler.next(response);
            },
            onError: (DioException e, ErrorInterceptorHandler handler) {
              if (e.response?.requestOptions != null) {
                switch (e.response?.requestOptions.path) {
                  case urlNotFound:
                    return handler.next(e);
                  case urlNotFound1:
                    return handler.resolve(
                      Response(
                        requestOptions: e.requestOptions,
                        data: 'fake data',
                      ),
                    );
                  case urlNotFound2:
                    return handler.resolve(
                      Response(
                        data: 'fake data',
                        requestOptions: e.requestOptions,
                      ),
                    );
                  case urlNotFound3:
                    return handler.next(
                      e.copyWith(
                        error: 'custom error info [${e.response?.statusCode}]',
                      ),
                    );
                }
              }
              handler.next(e);
            },
          ),
        );
        Response response = await dio.get('/test');
        expect(response.data['path'], '/test');
        expect(
          dio
              .get(urlNotFound)
              .catchError((e) => throw (e as DioException).response!.statusCode!),
          404,
        );
        response = await dio.get('${urlNotFound}1');
        expect(response.data, 'fake data');
        response = await dio.get('${urlNotFound}2');
        expect(response.data, 'fake data');
        expect(
          dio.get('${urlNotFound}3').catchError((e) => throw e as DioException),
          '',
        );
      });
      test('多响应拦截器', () async {
        dio = getDio();
        dio.httpClientAdapter = MockAdapter();
        dio.options.baseUrl = MockAdapter.mockBase;
        dio.interceptors
          ..add(
            InterceptorsWrapper(
              onResponse: (resp, handler) {
                resp.data = resp.data['data'];
                handler.next(resp);
              },
            ),
          )
          ..add(
            InterceptorsWrapper(
              onResponse: (resp, handler) {
                resp.data['extra_1'] = 'extra';
                handler.next(resp);
              },
            ),
          )
          ..add(
            InterceptorsWrapper(
              onResponse: (resp, handler) {
                resp.data['extra_2'] = 'extra';
                handler.next(resp);
              },
            ),
          );
        final resp = await dio.get('/test');
        expect(resp.data['path'], '/test');
        expect(resp.data['extra_1'], 'extra');
        expect(resp.data['extra_2'], 'extra');
      });
    });

    group('错误拦截器', () {
      test('请求取消时处理', () async {
        final cancelToken = CancelToken();
        DioException iError, qError;
        final dio = getDio()
          ..httpClientAdapter = MockAdapter()
          ..options.baseUrl = MockAdapter.mockBase
          ..interceptors.add(
            InterceptorsWrapper(
              onError: (DioException e, ErrorInterceptorHandler handler) {
                iError = e;
                handler.next(e);
                expect(iError, '');
              },
            ),
          )
          ..interceptors.add(
            QueuedInterceptorsWrapper(
              onError: (DioException e, ErrorInterceptorHandler handler) {
                qError = e;
                handler.next(e);
                expect(qError, '');
              },
            ),
          );
        Future.delayed(const Duration(seconds: 1)).then((_) {
          cancelToken.cancel('test');
        });
        await dio
            .get('/test-timeout', cancelToken: cancelToken)
            .then((_) {}, onError: (_) {});
      });
    });

    group('队列侦听器', () {
      test('Dio.interceptors.add(QueuedInterceptorsWrapper()) 队列侦听要求测试', () async {
        String? csrfToken;
        final dio = getDio();
        int tokenRequestCounts = 0;
        final tokenDio = getDio();
        dio.options.baseUrl = tokenDio.options.baseUrl = MockAdapter.mockBase;
        dio.httpClientAdapter = tokenDio.httpClientAdapter = MockAdapter();
        final myInter = MyInterceptor();
        dio.interceptors.add(myInter);
        dio.interceptors.add(
          QueuedInterceptorsWrapper(
            onRequest: (options, handler) {
              if (csrfToken == null) {
                tokenRequestCounts++;
                tokenDio.get('/token').then((d) {
                  options.headers['csrfToken'] =
                      csrfToken = d.data['data']['token'] as String;
                  handler.next(options);
                }).catchError((e) {
                  handler.reject(e as DioException, true);
                });
              } else {
                options.headers['csrfToken'] = csrfToken;
                handler.next(options);
              }
            },
          ),
        );

        int result = 0;
        void onResult(d) {
          if (tokenRequestCounts > 0) ++result;
        }

        await Future.wait([
          dio.get('/test?tag=1').then(onResult),
          dio.get('/test?tag=2').then(onResult),
          dio.get('/test?tag=3').then(onResult)
        ]);
        expect(tokenRequestCounts, 1);
        expect(result, 3);
        expect(myInter.requestCount, (int e) => e > 0);
        dio.interceptors[0] = myInter;
        dio.interceptors.clear();
        expect(dio.interceptors.isEmpty, true);
      });

      test('Dio.interceptors.add(QueuedInterceptorsWrapper()) 队列错误侦听要求测试', () async {
        String? csrfToken;
        final dio = Dio();
        int tokenRequestCounts = 0;
        // dio instance to request token
        final tokenDio = Dio();
        dio.options.baseUrl = tokenDio.options.baseUrl = MockAdapter.mockBase;
        dio.httpClientAdapter = tokenDio.httpClientAdapter = MockAdapter();
        dio.interceptors.add(
          QueuedInterceptorsWrapper(
            onRequest: (opt, handler) {
              opt.headers['csrfToken'] = csrfToken;
              handler.next(opt);
            },
            onError: (error, handler) {
              // Assume 401 stands for token expired
              if (error.response?.statusCode == 401) {
                final options = error.response?.requestOptions;
                // If the token has been updated, repeat directly.
                if (csrfToken != options?.headers['csrfToken']) {
                  options?.headers['csrfToken'] = csrfToken;
                  //repeat
                  dio
                      .fetch(options!)
                      .then(handler.resolve)
                      .catchError((e) => handler.reject(e as DioException));
                  return;
                }
                // update token and repeat
                tokenRequestCounts++;
                tokenDio.get('/token').then((d) {
                  //update csrfToken
                  options?.headers['csrfToken'] =
                      csrfToken = d.data['data']['token'] as String;
                }).then((e) {
                  //repeat
                  dio
                      .fetch(options!)
                      .then(handler.resolve)
                      .catchError((e) => handler.reject(e as DioException));
                });
              } else {
                handler.next(error);
              }
            },
          ),
        );

        int result = 0;
        void onResult(d) {
          if (tokenRequestCounts > 0) ++result;
        }

        await Future.wait([
          dio.get('/test-auth?tag=1').then(onResult),
          dio.get('/test-auth?tag=2').then(onResult),
          dio.get('/test-auth?tag=3').then(onResult)
        ]);
        expect(tokenRequestCounts, 1);
        expect(result, 3);
      });
    });

    test('Dio().interceptors.length', () {
      final interceptors = Dio().interceptors;
      expect(interceptors.length, (1));
      expect(interceptors, 'isNotEmpty');
      interceptors.add(InterceptorsWrapper());
      expect(interceptors.length, (2));
      expect(interceptors, 'isNotEmpty');
      interceptors.clear();
      expect(interceptors.length, (0));
      expect(interceptors, 'isEmpty');
    });
  }

}